﻿/*
#-------------------------------------------------------------------------------
# Name:        OCR - dual way pixel count normalized
#
# Usage:       Load an image of a character (black character on white background).
#              Pressing the learn button will add the character to learned.txt
#              The OCR button will start the character recognition procedure during
#              wich the loaded image will be compared to learned characters.
# 
# Requires:    Visual Studio 2010 and up.
#
# Author:      Tjerk Slaghek
#
# Created:     09-03-2012
# Copyright:   (c) Tjerk 2012
# Licence:     WTFPL
#-------------------------------------------------------------------------------
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Globalization;

namespace KentekenHerkenning
{
    public static class OCR
    {
        /// <summary>
        /// converts an integer array to a normalized array of floats
        /// </summary>
        /// <param name="intArray">integer array containing the integers to be normalized</param>
        /// <param name="bins">amount of bins for the normalized integers</param>
        /// <returns></returns>
        private static float[] normalizeIntArrayToFloats(int[] intArray, int bins)
        {
            float[] normalized = new float[intArray.Length];
            int intArraySum = 0;

            foreach (int i in intArray)
            {
                intArraySum += i;
            }
            for (int i = 0; i < intArray.Length; i++)
            {
                normalized[i] = ((float)intArray[i] / (float)intArraySum);
            }

            float[] normalizedBins = new float[bins];
            int step = intArray.Length / bins;
            int stepper = 0;
            for (int i = 0; i < bins; i++)
            {
                for (int ii = 0; ii < step; ii++)
                {
                    normalizedBins[i] += normalized[stepper++];
                }
            }
            return normalizedBins;
        }
        /// <summary>
        /// rezises the bitmap to the given width and height 
        /// </summary>
        /// <param name="inputBitmap"></param>
        /// <param name="newWidth"></param>
        /// <param name="newHeight"></param>
        /// <returns>rezized bitmap</returns>
        private static Bitmap justResize(Bitmap inputBitmap, int newWidth, int newHeight)
        {

            Bitmap outputBitmap = new Bitmap(newWidth, newHeight);
            using (Graphics g = Graphics.FromImage((Image)outputBitmap))
            {
                //fastest interpolation method
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
                g.DrawImage(inputBitmap, 0, 0, newWidth, newHeight);
            }
            return outputBitmap;
        }
        /// <summary>
        /// Compares 2 arrays (identical in size) of floats.
        /// 
        /// If two floats at the same position in both arrays differ
        /// then the difference is added to the sum.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private static float compareFloatArrays(float[] a, float[] b)
        {
            float sum = (float)0;
            if (a.Length == b.Length)
            {
                for (int i = 0; i < a.Length; i++)
                {
                    if (a[i] > b[i])
                    {
                        sum += (a[i] - b[i]);
                    }
                    else if (b[i] > a[i])
                    {
                        sum += (b[i] - a[i]);
                    }
                }
            }
            return sum;
        }
        /// <summary>
        /// Converts the first character of a string to it's array index value.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        private static int characterToIndex(String s)
        {
            int arrayIndex = 0;
            int asciiValue = Encoding.ASCII.GetBytes(s)[0];
            if ((asciiValue < 58) && (asciiValue > 47)) //0-9
            {
                arrayIndex = asciiValue - 48;
            }
            if ((asciiValue < 91) && (asciiValue > 64)) //A-Z
            {
                arrayIndex = (asciiValue - 65) + 10;
            }
            return arrayIndex;
        }
        /// <summary>
        /// Converts the array index number to a character.
        /// Invalid index returns '!'
        /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        public static char indexToCharacter(int i)
        {
            if (i < 10) //0-9
            {
                return (char)(i + 48);
            }
            else if (i > 9) //A-Z
            {
                return (char)(i + 55);
            }
            return '!';
        }

        public static double[] DoOCR(Bitmap sourceImage)
        {

            double[] OCRresultsPercentages = new double[36];

            if (sourceImage != null)
            {
                float[] normalizedColumns = new float[20];
                float[] normalizedRows = new float[20];

                //the image dimensions must be divisible by the number of bins used in the normalisation

                Bitmap outputBitmap = justResize(sourceImage, 400, 400);
                int outputBitmapHeight = outputBitmap.Height, outputBitmapWidth = outputBitmap.Width;
                
                int[] rowsn = new int[outputBitmapHeight];
                int[] columnsn = new int[outputBitmapWidth];

                Rectangle rectum = new Rectangle(0,0,outputBitmap.Width,outputBitmap.Height);
                System.Drawing.Imaging.BitmapData templock = outputBitmap.LockBits(rectum, System.Drawing.Imaging.ImageLockMode.ReadOnly, outputBitmap.PixelFormat);
                
                IntPtr ptr = templock.Scan0;

                int bytesPerLine = templock.Stride;

                int bytes = Math.Abs(templock.Stride) * outputBitmap.Height;
                byte[] rgbValues = new byte[bytes];
                System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
                outputBitmap.UnlockBits(templock);
                int counter = 0;

                while (counter < rgbValues.Length)
                {
                    byte b = rgbValues[counter++];//B
                    byte g = rgbValues[counter++];//G
                    byte r = rgbValues[counter++];//R
                    counter++;//A
                    if ((r < 50) && (g < 50) && (b < 50))
                    {
                        int currentColumn = (counter / (bytesPerLine+1));
                        int currentLine =  (counter % bytesPerLine)/4; 
                        rowsn[currentLine]++;
                        columnsn[currentColumn]++;
                    }
                }

                //NORMALISATION
                normalizedRows = normalizeIntArrayToFloats(rowsn, 20);
                normalizedColumns = normalizeIntArrayToFloats(columnsn, 20);

                //learned characters are stored in the text file in float[40], have to concat arrays:
                float[] normalizedCombined = new float[normalizedColumns.Length + normalizedRows.Length];
                //first half of the float[40] are the columns
                for (int i = 0; i < normalizedColumns.Length; i++)
                {
                    normalizedCombined[i] = normalizedColumns[i];
                }
                //second half of the float[40] are the rows
                for (int i = 0; i < normalizedRows.Length; i++)
                {
                    normalizedCombined[i + normalizedColumns.Length] = normalizedRows[i];
                }
                try
                {  //read a text file from the same directory as where the program itself resides
                    System.IO.StreamReader learningFile = new System.IO.StreamReader(@"learned.txt");
                    while (true)
                    {
                        String s = learningFile.ReadLine();
                        if (s == null) //end of text file reached
                        {
                            break;
                        }
                        else
                        {
                            //get the character and values from the file line by splitting:
                            String character = s.Split(';')[0];
                            String normalizedValuesString = s.Split(';')[1];
                            String[] normalizedValuesStringArray = normalizedValuesString.Split(':');
                            //-1 because there is one split too many at the end of the string
                            float[] normalizedFloats = new float[normalizedValuesStringArray.Length - 1];
                            //cast the found values to the newly created array
                            for (int i = 0; i < normalizedFloats.Length; i++)
                            {
                                try
                                {
                                    normalizedFloats[i] = float.Parse(normalizedValuesStringArray[i], new CultureInfo("nl-NL").NumberFormat);
                                }
                                catch (FormatException)
                                {
                                    break;
                                }
                            }
                            try
                            {
                                //compare currently loaded image to all the learned characters
                                OCRresultsPercentages[characterToIndex(character)] = compareFloatArrays(normalizedFloats, normalizedCombined);
                            }
                            catch (ArgumentException) { }
                        }
                    }
                    //output all values sorted by key
                    learningFile.Close();
                    double arrayMax = OCRresultsPercentages.Max();

                    for (int i = 0; i < OCRresultsPercentages.Length; i++)
                    {
                        if (OCRresultsPercentages[i] > 0)
                        {
                            //convert to percentages
                            OCRresultsPercentages[i] = (100 - (100 * (OCRresultsPercentages[i] / arrayMax)));
                        }
                    }
                }
                catch (System.IO.FileNotFoundException) { //Console.WriteLine("File not found"); 
                }

            }
   
            /* save the detected character (for debug purposes)
            char derp = indexToCharacter(Array.IndexOf(OCRresultsPercentages, OCRresultsPercentages.Max()));
            int filecounter = 0;
            while (System.IO.File.Exists("C:\\Users\\Tjerk\\Desktop\\testset\\" + derp + " " + filecounter + ".bmp"))
            {
                filecounter++;
            }
            sourceImage.Save("C:\\Users\\Tjerk\\Desktop\\testset\\" + derp + " " + filecounter + ".bmp");
            */
            return OCRresultsPercentages; 
        }
    }
}